/******************************************************************************
 * SAGE - Scalable Adaptive Graphics Environment
 *
 * Module: sageCircBuf.cpp - the circular buffer to store streamed pixel blocks
 * Author : Byungil Jeong
 *
 * Copyright (C) 2004 Electronic Visualization Laboratory,
 * University of Illinois at Chicago
 *
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the distribution.
 *  * Neither the name of the University of Illinois at Chicago nor
 *    the names of its contributors may be used to endorse or promote
 *    products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Direct questions, comments etc about SAGE to bijeong@evl.uic.edu or 
 * http://www.evl.uic.edu/cavern/forum/
 *
 *****************************************************************************/

#include "sageCircBuf.h"

int sageCircBuf::init(int blockNum)
{
   writeIdx = readIdx = 0;
   noOfBlocks = blockNum;
   blockArray = new sageBlock[noOfBlocks];

   bufLock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
   pthread_mutex_init(bufLock, NULL);
//   notFull = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
//   pthread_cond_init(notFull, NULL);
   notEmpty = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));
   pthread_cond_init(notEmpty, NULL);

   readerList.clear();
   addReader();
   
   return 0;
}//End of sageCircBuf()

int sageCircBuf::addReader()
{
   pthread_mutex_lock(bufLock);
   readerInfo rInfo;
   rInfo.readIdx = readIdx;
   rInfo.empty = (readIdx == writeIdx);
   readerList.push_back(rInfo);
   pthread_mutex_unlock(bufLock);
   
   return readerList.size();
}

int sageCircBuf::removeReader(int readerID)
{
   pthread_mutex_lock(bufLock);
   readerInfo rInfo;
   rInfo.readIdx = readIdx;
   rInfo.empty = (readIdx == writeIdx);
   readerList[readerID].readIdx = -1;
   pthread_mutex_unlock(bufLock);
   
   return readerList.size();
}

sageBlock* sageCircBuf::readBlock()
{
   pthread_mutex_lock(bufLock);
   if (empty) {
      pthread_mutex_unlock(bufLock);
      return NULL;
   }
   else   
      return &blockArray[readIdx];
}

sageBlock* sageCircBuf::readBlock(int id, bool blocking)
{
   pthread_mutex_lock(bufLock);
   
   if (blocking) {
      while(readerList[id].empty) {
         //std::cout << "buffer is empty" << std::endl;
         pthread_cond_wait(notEmpty, bufLock);
         //std::cout << "buffer is not empty" << std::endl;
      }
   }
   else {
      if (readerList[id].empty) {
         pthread_mutex_unlock(bufLock);
         return NULL;
      }   
   }

   sageBlock *block = NULL;
   if (readerList[id].readIdx >= 0)
      block = &blockArray[readerList[id].readIdx];
      
   pthread_mutex_unlock(bufLock);   
         
   return block;
}

int sageCircBuf::distToWriteIdx(int ridx)
{
   int dist = 0;
   
   if (writeIdx >= ridx)
      dist = writeIdx - ridx;
   else 
      dist = writeIdx - ridx + noOfBlocks;   
      
   return dist;   
}   

int sageCircBuf::updateReadIdx(int id, int bufferMode)
{
   pthread_mutex_lock(bufLock);

   if (readerList[id].readIdx < 0) {
      pthread_mutex_unlock(bufLock);
      return -1;
   }   
   
   int minDist = 100000;
   int minIdx = -1;
   int readerNum = readerList.size();
   
   for (int i=0; i<readerNum; i++) {
      if (readerList[i].readIdx >= 0) {
         if (minDist > distToWriteIdx(readerList[i].readIdx)) {
            minDist = distToWriteIdx(readerList[i].readIdx);
            minIdx = i;
         }
      }
   }

   int distDiff = distToWriteIdx(readerList[id].readIdx) - minDist;

   if (minIdx >= 0 && distDiff > 1 && bufferMode == 1) {
      readerList[id].readIdx = (readerList[id].readIdx + distDiff) % noOfBlocks;
      std::cout << "drop " << distDiff-1 << " frames" << std::endl;
   }   
   else
      readerList[id].readIdx = (readerList[id].readIdx + 1) % noOfBlocks;

   //std::cout << "increase reader " << id << " : " << readerList[id].readIdx << std::endl;
   //std::cout << "writeIdx " << writeIdx << std::endl;

   if (readerList[id].readIdx == writeIdx)
      readerList[id].empty = true;

   int maxDist = 0;
   for (int i=0; i<readerNum; i++) {
      if (readerList[i].readIdx >= 0)
         maxDist = MAX(maxDist, distToWriteIdx(readerList[i].readIdx));
   }   

   distDiff = distToWriteIdx(readIdx) - maxDist;
   
   if (distDiff > 0) {
      readIdx = (readIdx + distDiff)%noOfBlocks;

      //std::cout << "increase read idx " << readIdx << std::endl;
      full = false;
      
      pthread_mutex_unlock(bufLock);
      //std::cout << "not full signal" << std::endl;
      //pthread_cond_signal(notFull);
   }
   else   
      pthread_mutex_unlock(bufLock);
      
   return 0;
}

int sageCircBuf::updateReadIdx(int id)
{
   pthread_mutex_lock(bufLock);

   if (readerList[id].readIdx < 0) {
      pthread_mutex_unlock(bufLock);
      return -1;
   }   
   
   readerList[id].readIdx = (readerList[id].readIdx + 1) % noOfBlocks;
   //std::cout << "increase reader " << id << " : " << readerList[id].readIdx << std::endl;
   //std::cout << "writeIdx " << writeIdx << std::endl;

   if (readerList[id].readIdx == writeIdx)
      readerList[id].empty = true;

   int readerNum = readerList.size();
   bool increaseIdx = true;
   for (int i=0; i<readerNum; i++) {
      if (readerList[i].readIdx >= 0)
         increaseIdx &= distToWriteIdx(readerList[i].readIdx) < distToWriteIdx(readIdx);
   }   
   
   if (increaseIdx) {
      readIdx = (readIdx+1)%noOfBlocks;    
      //std::cout << "increase read idx " << readIdx << std::endl;
      full = false;
      
      pthread_mutex_unlock(bufLock);
      //std::cout << "not full signal" << std::endl;
      //pthread_cond_signal(notFull);
   }
   else   
      pthread_mutex_unlock(bufLock);
      
   return 0;
}

int sageCircBuf::updateReadIdx()
{
   readIdx = (readIdx+1) % noOfBlocks;
   full = false;
   if (readIdx == writeIdx)
      empty = true;
      
   pthread_mutex_unlock(bufLock);
   //std::cout << "not full signal" << std::endl;
   //pthread_cond_signal(notFull);
   
   return 0;
}

sageBlock* sageCircBuf::readBlock(int frameNum)
{
   pthread_mutex_lock(bufLock);
   if (empty) {
      //std::cout << "empty buffer" << std::endl;
      pthread_mutex_unlock(bufLock);
      return NULL;
   }
   else   {   
      if (frameNum%10000 >= blockArray[readIdx].frameNum)
         return &blockArray[readIdx];
      else {
         pthread_mutex_unlock(bufLock);
         //std::cout << "frame number mismatch " << frameNum%10000 << " " << readPt->frameNum << 
         //" block flag = " << readPt->flag << std::endl;
      }   
   }   
      
   return NULL;
}

sageBlock* sageCircBuf::getNextWriteBlock()
{
   if (full) {
      //std::cout << "buffer full " << std::endl;
      //sage::usleep(1);
      return NULL;
   }   
      
   sageBlock *block = &blockArray[writeIdx];
   
   if (block->reformatted) {
      block->subWidth = pixelBlockWidth;
      block->subHeight = pixelBlockHeight;
      block->bytesPerPixel = bytesPerPixel;
   }   

   return block;
}

int sageCircBuf::updateWriteIdx()
{
   pthread_mutex_lock(bufLock);      

   writeIdx = (writeIdx+1) % noOfBlocks;
   int nextIdx = (writeIdx+1) % noOfBlocks;
   if (nextIdx == readIdx) {
      full = true;
   }   

   empty = false;

   int readerNum = readerList.size();
   for (int i=0; i<readerNum; i++) 
      readerList[i].empty = false;
   
   pthread_cond_signal(notEmpty);
   
/*
   while(full) {
      //std::cout << "buffer is full" << std::endl;
      pthread_cond_wait(notFull, bufLock);
      //std::cout << "buffer is not full" << std::endl;
   }
*/   
   pthread_mutex_unlock(bufLock);
   
   return 0;
}

void sageCircBuf::storeBlockConfig(sageBlock *block)
{
   pixelBlockWidth = block->subWidth;
   pixelBlockHeight = block->subHeight;
   bytesPerPixel = block->bytesPerPixel;
   
   for (int i=0; i<noOfBlocks; i++)
      blockArray[i].reformatted = true;   
}

sageCircBuf::~sageCircBuf()
{
/*
   if (bufLock)
      free(bufLock);
   
   if (notFull)   
      free(notFull);
   
   if (blockArray)   
      delete [] blockArray;
*/      
}//End of ~sageCircBuf()
